package org.feifei.hrm.service.impl;

import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import org.feifei.hrm.domain.CourseType;
import org.feifei.hrm.except.ErrorCode;
import org.feifei.hrm.except.ValidUtil;
import org.feifei.hrm.mapper.CourseTypeMapper;
import org.feifei.hrm.service.ICourseTypeService;
import org.feifei.hrm.vo.CrumbsVo;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;

/**
 * <p>
 * 课程目录 服务实现类
 * </p>
 *
 * @author feifei
 * @since 2022-01-06
 */
@Service
public class CourseTypeServiceImpl extends ServiceImpl<CourseTypeMapper, CourseType> implements ICourseTypeService {

    private static  final  String COURSETYPE_TREEDATA_IN_CACHEC = "courseType_treeData_in_cache";//redis的key

    @Resource
    private CourseTypeMapper courseTypeMapper;

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private RedissonClient redissonClient;

    @Override
    public List<CourseType> getTree(Long pid) {
        // 第一种方式，通过java递归调用
//         return loadRecursionById(pid);
        // 方案二 mysql中循环调用
//      return courseTypeMapper.getTree(pid);
        // 第三种方案，循环遍历


        List<CourseType> courseTypes  = (List<CourseType>) redisTemplate.opsForValue().get(COURSETYPE_TREEDATA_IN_CACHEC);
        if (courseTypes  != null) {
            return courseTypes ;
        } else {
//            // 双检索 --- 单机方案，不支持集群
//            synchronized (this) {
//                if (list == null) {
//                    // 1 缓存穿透，方案1:没有数据也放一个null（实现了），方案2：布隆过滤器
//                    // 2 缓存击穿，缓存过期一瞬间来高并发。 方案1：永不过期（实现了） 方案2：单机锁（不支持集群）（实现了） //@TODO 方案3：分布式锁（支持集群）
//                    // 3 缓存雪崩 ，方案1：缓存永不过期，缓存预热（实现了） 方案2：让各个缓存数据过期时间不一样
//                    List<CourseType> courseTypes = getByLoop(pid);
//                    redisTemplate.opsForValue().set(COURSETYPE_TREEDATA_IN_CACHEC,courseTypes);
//                    return courseTypes;
//                }
//            }
//            return list;

            RLock lock = null;
            try {
                lock = redissonClient.getLock(COURSETYPE_TREEDATA_IN_CACHEC);
                lock.lock();
                //            synchronized (this.getClass()){
                courseTypes = (List<CourseType>) redisTemplate.opsForValue().get(COURSETYPE_TREEDATA_IN_CACHEC);
                if (courseTypes == null) {
                    // （1）第一个请求会进来
                    List<CourseType> courseTypesFromDb = getByLoop(pid);
                    // 1 缓存穿透，方案1:没有数据也放一个null（实现了），方案2：布隆过滤器
                    // 2 缓存击穿，缓存过期一瞬间来高并发。 方案1：永不过期（实现了） 方案2：单机锁（不支持集群）（实现了） //@TODO 方案3：分布式锁（支持集群）
                    // 3 缓存雪崩 ，方案1：缓存永不过期，缓存预热（实现了） 方案2：让各个缓存数据过期时间不一样
                    redisTemplate.opsForValue().set(COURSETYPE_TREEDATA_IN_CACHEC, courseTypesFromDb);

                    return courseTypesFromDb;
                }
                //(2)其他请求会进来
                return courseTypes;
//            }
            } finally {
                if (lock != null) {
                    lock.unlock();
                }

            }
        }
    }

    @Override
    public List<CrumbsVo> queryCrumbs(Long typeId) {
        //0 定义返回类型List
        List<CrumbsVo> list = new ArrayList<>();
        //1 校验
        ValidUtil.assertNotNull(typeId, ErrorCode.CODE_400_COURSE_TYPE_CRUMBS_ILLEGAL);
        //2 通过typeId获取Type进而获取path
        String path = courseTypeMapper.selectById(typeId).getPath();
        //3 通过.分割得到typeIds [1,2,3]
        String[] typeIds = path.split("\\.");
        //4 遍历，每个节点构造CrumbsVo放过入List
        for (String idStr : typeIds) {
            CrumbsVo crumbsVo = new CrumbsVo();
            Long id = Long.valueOf(idStr);
            //4.1通过typeId,查询课程可以构造自己                          1 2 3
            CourseType owner = courseTypeMapper.selectById(id);
            crumbsVo.setOwnerCourseType(owner);
            //4.2 通过自己的pid获取所有的儿子排除自己
            Long pid = owner.getPid();
            List<CourseType> otherCourseTypes = courseTypeMapper
                    .selectList(new EntityWrapper<CourseType>().eq("pid", pid));
            Iterator<CourseType> iterator = otherCourseTypes.iterator();
            while (iterator.hasNext()){
                CourseType courseType = iterator.next();
                if (id.longValue()==courseType.getId().longValue()){
                    iterator.remove();
                    break;
                }
            }
            crumbsVo.setOtherCourseTypes(otherCourseTypes);
            list.add(crumbsVo);
        }
        //5 返回List
        return list;
    }

    private List<CourseType> getByLoop(Long pid) {
        List<CourseType> courseTypes = courseTypeMapper.selectList(null);
        Map<Long, CourseType> map = new HashMap<>();
        List<CourseType> list = new ArrayList<>();
        courseTypes.forEach(courseType -> map.put(courseType.getId(), courseType));
        courseTypes.forEach(courseType -> {
            if (courseType.getPid() == pid) {
                list.add(courseType);
            } else {
                CourseType parent = map.get(courseType.getPid());
                parent.getChildren().add(courseType);
            }
        });

        return list;
    }

    public List<CourseType> loadRecursionById(Long id) {
        List<CourseType> courseTypes = courseTypeMapper.selectList(new EntityWrapper<CourseType>().eq("pid", id));

        if (courseTypes == null || courseTypes.size() < 1) {
            return null;
        }
        courseTypes.forEach(courseType -> {
            List<CourseType> courseTypeChilds = loadRecursionById(courseType.getId());
            courseType.setChildren(courseTypeChilds);
        });
        return courseTypes;
    }


    /**
     * 延迟双删模式，解决双写一致性问题：即当有两个线程A和B   前提：先操作数据库再操作缓存
     * 请求B去数据库查询旧值
     * 请求A将新值写入数据库
     * 请求A删除缓存
     * 请求B将旧值写入缓存
     * 该请求有个前提，A写入数据库的时间间隔小于 B查询数据库的时间
     * @param entity
     * @return
     */
    @Override
    public boolean updateById(CourseType entity) {
        boolean flag = super.updateById(entity);
        redisTemplate.delete(COURSETYPE_TREEDATA_IN_CACHEC);
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        redisTemplate.delete(COURSETYPE_TREEDATA_IN_CACHEC);
        return flag;
    }
}
